package com.colabo.j2ee.web.core.persistence;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Hibernate;
import org.hibernate.usertype.UserType;

/**
 * 该类是针对Hibernate的自定义数据类型的一个实现参考。 使用方法：<br>
 * 
 * <pre>
 * 		假设数据库中的某字段数据类型为varchar,而对应的pojo域的数据类型为util.Date,则需要通过自定义数据类型进行转化
 * <br>
 * 		Hibernate配置文件中类似如下：
 * 　　　&lt;property name=&quot;createdTime&quot; type=&quot;com.cib.cap.base.common.hib.DateToStringUserType&quot;&gt;
 *             &lt;column name=&quot;CREATEDTIME&quot; length=&quot;19&quot; /&gt;
 *      &lt;/property&gt;
 * </pre>
 */
public class DateToStringUserType implements UserType, Serializable {
	private static final long serialVersionUID = 1L;

	protected final Log logger = LogFactory.getLog(this.getClass());

	/**
	 * 时间字符串
	 */
	private String timeString;

	/** 
	 * 日期格式hh:mm:ss表示12小时制, HH:mm:ss表示24小时制 
	 */
	public static final String DATA_TO_STRING_FORMATE = "yyyy-MM-dd HH:mm:ss";

	/** 
	 * 有几个字段就有几个值，这里容易出错，要多注意
	 */
	private static final int[] SQL_TYPES = { Types.VARCHAR };

	/** 这个方法告诉Hibernate在成生DDL时对列采用什么样的SQL语法 */
	public int[] sqlTypes() {
		return SQL_TYPES;
	}

	/**
	 * Hibernate返回什么样的映射类型.
	 */
	public Class returnedClass() {
		return Date.class;
	}

	/**
	 * 表明这个类的实例在创建以后就不可以改变属性。Hibernate能为不可改变的类作一些性能优化。
	 */
	public boolean isMutable() {
		return true;
	}

	/**
	 * 由于DateToString是不可变的，所以深拷贝可以直接返回对象引用。
	 * 拷贝的对象由应用程序使用，而原版对象由Hibernate维护以做脏数据检查 提供自定义类型的完全复制方法 本方法将用构造返回对象
	 * 当nullSafeGet方法调用之后，我们获得了自定义数据对象，在向用户返回自定义数据之前，
	 * deepCopy方法将被调用，它将根据自定义数据对象构造一个完全拷贝，并将此拷贝返回给用户
	 * 此时我们就得到了自定义数据对象的两个版本，第一个是从数据库读出的原始版本，其二是我们通过
	 * deepCopy方法构造的复制版本，原始的版本将有Hibernate维护，复制版由用户使用。
	 * 原始版本用作稍后的脏数据检查依据；Hibernate将在脏数据检查过程中将两个版本的数据进行对比（通过调用
	 * equals方法），如果数据发生了变化（equals方法返回false），则执行对应的持久化操作
	 */
	public Object deepCopy(Object value) {
		return value;
	}

	/**
	 * 两个对象是否相等， 此方法将用作脏数据检查，参数x、y分别为数据的两个副本
	 * 如果equals方法返回false,则Hibernate将认为数据发生变化,并将变化更新到数据库表中
	 */
	public boolean equals(Object x, Object y) {
		return ObjectUtils.equals(x, y);
	}

	/** 
	 * 得到hash码 ，参看了jdk的hashcode算法 
	 */
	public int hashCode(Object x){
		int h = x.hashCode();

		h += ~(h << 9);
		h ^= (h >>> 14);
		h += (h << 4);
		h ^= (h >>> 10);
		return h;
	}

	/**
	 * 从数据库到Class的类型映射转换
	 */
	public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner) throws SQLException {
		Hibernate.STRING.nullSafeGet(resultSet, names[0]);
		if (resultSet.wasNull()) {
//			logger.warn("returning null as resultSet. ");
			return null;
		}

		String dtStr = resultSet.getString(names[0]);
		if (dtStr == null || dtStr.trim().length() <= 0) {
//			logger.warn("returning null as column: " + names[0]);
			return null;
		}

		dtStr = dtStr.trim();
		Date date = toDate(dtStr, DATA_TO_STRING_FORMATE);
		return date;
	}

	/**
	 * 把java.Util.Date转为字符型日期数据
	 * 
	 * @param d
	 *            java.Util.Date日期
	 * @param pattern
	 *            日期转换格式
	 * @return 字符型日期
	 */
	public static String formatDate(java.util.Date d, String pattern) {
		SimpleDateFormat formatter = (SimpleDateFormat) SimpleDateFormat.getDateInstance();
		formatter.applyPattern(pattern);
		return formatter.format(d);
	}

	/**
	 * 把字符型日期数据转为java.Util.Date型
	 * 
	 * @param sDate
	 *            字符型日期
	 * @param sFmt
	 *            日期转换格式
	 * @return java.Util.Date
	 */
	public static java.util.Date toDate(String sDate, String sFmt) {
		SimpleDateFormat sdfFrom = null;
		java.util.Date dt = null;
		try {
			if (sDate == null) {
				return null;
			}
			sdfFrom = new SimpleDateFormat(sFmt);
			dt = sdfFrom.parse(sDate);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		} finally {
			sdfFrom = null;
		}

		return dt;
	}

	/**
	 * 从Class到数据库的类型映射转换
	 */
	public void nullSafeSet(PreparedStatement statement, Object value, int index) throws SQLException {
		if (value == null) {
			statement.setNull(index, Types.VARCHAR);
		} else {
			String dt = formatDate((Date) value, DATA_TO_STRING_FORMATE);
			statement.setString(index, dt);
		}
	}

	/**
	 * 当把DateToString类型数据写入二级缓存时，此方法被调用
	 */
	public Serializable disassemble(Object value) {
		return null;
	}

	/**
	 * 当从二级缓存中读取DateToString类型数据时，此方法被调用
	 */
	public Object assemble(Serializable cached, Object owner) {
		return null;
	}
	
	/**
	 * 替换
	 */
	public Object replace(Object original, Object target, Object owner) {
		return original;
	}

	public DateToStringUserType() {
		super();
	}

	public DateToStringUserType(Date date) {
		super();
		this.timeString = formatDate(date, DATA_TO_STRING_FORMATE);
	}

	/**
	 * 得到字符
	 */
	public String toString() {
		return this.timeString;
	}

	public static int[] getSQL_TYPES() {
		return SQL_TYPES;
	}

	public String getTimeString() {
		return timeString;
	}

	public void setTimeString(String timeString) {
		this.timeString = timeString;
	}
}
